#ifndef _SRC_NET_EVENT_LOOP_CC_
#define _SRC_NET_EVENT_LOOP_CC_

#include <any>
#include <atomic>
#include <functional>
#include <mutex>
#include <thread>

#include "src/base/time_stamp.h"
#include "src/base/utils.h"
#include "src/net/callback.h"
#include "src/net/timer_id.h"

namespace fio66::net {
class Channel;
class Poller;
class TimerQueue;

class EventLoop {
  DISALLOW_COPY_AND_ASSIGN(EventLoop)
 public:
  typedef std::function<void()> Functor;

 public:
  EventLoop();
  ~EventLoop();  // force out-line dtor, for std::unique_ptr members.
                 ///
  /// Loops forever.
  ///
  /// Must be called in the same thread as creation of the object.
  ///
  void Loop();

  /// Quits loop.
  ///
  /// This is not 100% thread safe, if you call through a raw pointer,
  /// better to call through shared_ptr<EventLoop> for 100% safety.
  void Quit();

  ///
  /// Time when poll returns, usually means data arrival.
  ///
  Timestamp PollReturnTime() const { return poll_return_time_; }

  int64_t Iteration() const { return iteration_; }

  /// Runs callback immediately in the loop thread.
  /// It wakes up the loop, and run the cb.
  /// If in the same loop thread, cb is run within the function.
  /// Safe to call from other threads.
  void RunInLoop(Functor cb);
  /// Queues callback in the loop thread.
  /// Runs after finish pooling.
  /// Safe to call from other threads.
  void QueueInLoop(Functor cb);

  size_t QueueSize();

  // timers

  ///
  /// Runs callback at 'time'.
  /// Safe to call from other threads.
  ///
  TimerId RunAt(Timestamp time, TimerCallback cb);
  ///
  /// Runs callback after @c delay seconds.
  /// Safe to call from other threads.
  ///
  TimerId RunAfter(double delay, TimerCallback cb);
  ///
  /// Runs callback every @c interval seconds.
  /// Safe to call from other threads.
  ///
  TimerId RunEvery(double interval, TimerCallback cb);
  ///
  /// Cancels the timer.
  /// Safe to call from other threads.
  ///
  void Cancel(TimerId timerId);

  // internal usage
  void Wakeup();
  void UpdateChannel(Channel* channel);
  void RemoveChannel(Channel* channel);
  bool HasChannel(Channel* channel);

  // pid_t threadId() const { return threadId_; }
  void AssertInLoopThread() {
    if (!IsInLoopThread()) {
      AbortNotInLoopThread();
    }
  }
  bool IsInLoopThread() const { return thread_id_ == std::this_thread::get_id(); }
  // bool callingPendingFunctors() const { return callingPendingFunctors_; }
  bool EventHandling() const { return event_handling_; }

  void SetContext(const std::any& context) { context_ = context; }

  const std::any& GetContext() const { return context_; }

  std::any* GetMutableContext() { return &context_; }

  static EventLoop* GetEventLoopOfCurrentThread();

 private:
  void AbortNotInLoopThread();
  void HandleRead();  // waked up
  void DoPendingFunctors();

  void PrintActiveChannels() const;  // DEBUG

  typedef std::vector<Channel*> ChannelList;

  bool looping_; /* atomic */
  std::atomic<bool> quit_;
  bool event_handling_;           /* atomic */
  bool calling_pengding_functors_;          /* atomic */
  int64_t iteration_;
  const std::thread::id thread_id_;
  Timestamp poll_return_time_;
  std::unique_ptr<Poller> poller_;
  std::unique_ptr<TimerQueue> timer_queue_;
  int wakeup_fd_;
  // unlike in TimerQueue, which is an internal class,
  // we don't expose Channel to client.
  std::unique_ptr<Channel> wakeup_channel_;
  std::any context_;

  // scratch variables
  ChannelList active_channels_;
  Channel* current_active_channel_;

  std::mutex mutex_;
  std::vector<Functor> pending_functors_;
};

}  // namespace fio66::net

#endif  // _SRC_NET_EVENT_LOOP_CC_
